/* md4.c : md4 
 * Copyright (C) 2012, drangon <drangon.zhou@gmail.com>
 * 2012-07
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <dgnsc/md4.h>
#include <dgnsc/base64.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct dgn_md4_st
{
	uint32_t mb[4]; // A B C D
	int64_t total_len; // bits
	int32_t last_len; // last buffer not process, < 64
	char last_buf[64];
};

static void process_data( dgn_md4_t md4, const char * data );

dgn_md4_t dgn_md4_init()
{
	struct dgn_md4_st * md4 = (struct dgn_md4_st *)malloc( sizeof(struct dgn_md4_st) );
	md4->mb[0] = 0x67452301;
	md4->mb[1] = 0xefcdab89;
	md4->mb[2] = 0x98badcfe;
	md4->mb[3] = 0x10325476;
	md4->total_len = 0;
	md4->last_len = 0;
	memset( md4->last_buf, 0, sizeof(md4->last_buf) );
	
	return md4;
}

void dgn_md4_reset( dgn_md4_t md4 )
{
	md4->mb[0] = 0x67452301;
	md4->mb[1] = 0xefcdab89;
	md4->mb[2] = 0x98badcfe;
	md4->mb[3] = 0x10325476;
	md4->total_len = 0;
	md4->last_len = 0;
	memset( md4->last_buf, 0, sizeof(md4->last_buf) );
	
	return;
}

void dgn_md4_pushdata( dgn_md4_t md4, const char * data, int len )
{
	int curr_pos = 0;

	if( data == NULL && len < 0 )
		return;

	// process last buf first
	if( md4->last_len > 0 ) {
		if( md4->last_len + len < 64 ) {
			memcpy( md4->last_buf + md4->last_len, data, len );
			md4->last_len += len;
			return;
		}
		// process last buf and data;
		memcpy( md4->last_buf + md4->last_len, data, 64 - md4->last_len );
		process_data( md4, md4->last_buf );
		md4->total_len += 512;

		curr_pos = 64 - md4->last_len;
		md4->last_len = 0;
	}
	
	for( ; curr_pos + 64 <= len; curr_pos += 64 ) {
		process_data( md4, data + curr_pos );
		md4->total_len += 512;
	}
	
	md4->last_len = len - curr_pos;
	if( md4->last_len > 0 ) 
		memcpy( md4->last_buf, data + curr_pos, md4->last_len );

	return;
}

uint8_t * dgn_md4_finish( dgn_md4_t md4 )
{
	md4->total_len += md4->last_len * 8;

	if( md4->last_len >= 56 ) {
		md4->last_buf[md4->last_len] = 0x80;
		memset( md4->last_buf + md4->last_len + 1, 0, 64 - md4->last_len - 1 );
		process_data( md4, md4->last_buf );
		memset( md4->last_buf, 0, 56 );
	}
	else {
		md4->last_buf[md4->last_len] = 0x80;
		memset( md4->last_buf + md4->last_len + 1, 0, 56 - md4->last_len - 1 );
	}
	
	memcpy( md4->last_buf + 56, (const char *)&(md4->total_len), 8 );
	process_data( md4, md4->last_buf );

	// all data finished, create message
	return (uint8_t *)md4->mb;
}

uint8_t * dgn_md4_get( dgn_md4_t md4 )
{
	return (uint8_t *)md4->mb;
}

void dgn_md4_getstr( dgn_md4_t md4, char md4str[33] )
{
	dgn_base16_enc( md4str, (char *)md4->mb, 16 );
	md4str[32] = '\0';
	return;
}

void dgn_md4_fini( dgn_md4_t md4 )
{
	if( md4 != NULL ) {
		free( md4 );
	}
	return;
}

static void process_data( dgn_md4_t md4, const char * data )
{
	uint32_t runmb[4];
	uint32_t * xx = NULL;

	runmb[0] = md4->mb[0];
	runmb[1] = md4->mb[1];
	runmb[2] = md4->mb[2];
	runmb[3] = md4->mb[3];

	xx = (uint32_t *)data;

	// rotate shift left
#define RSL(x, n) ( ((x) << (n)) | ((x) >> (32 - (n))) )
	
	// Round 1
#define F(x,y,z) ( ((x) & (y)) | ( ~(x) & (z) ) )
#define R1(a,b,c,d,x,s) \
	(a) += F( (b), (c), (d) ) + (x); \
	(a) = RSL( (a), (s) )
#define S11	3
#define S12	7
#define S13	11
#define S14	19

	R1( runmb[0], runmb[1], runmb[2], runmb[3], xx[0], S11 );
	R1( runmb[3], runmb[0], runmb[1], runmb[2], xx[1], S12 );
	R1( runmb[2], runmb[3], runmb[0], runmb[1], xx[2], S13 );
	R1( runmb[1], runmb[2], runmb[3], runmb[0], xx[3], S14 );

	R1( runmb[0], runmb[1], runmb[2], runmb[3], xx[4], S11 );
	R1( runmb[3], runmb[0], runmb[1], runmb[2], xx[5], S12 );
	R1( runmb[2], runmb[3], runmb[0], runmb[1], xx[6], S13 );
	R1( runmb[1], runmb[2], runmb[3], runmb[0], xx[7], S14 );

	R1( runmb[0], runmb[1], runmb[2], runmb[3], xx[8], S11 );
	R1( runmb[3], runmb[0], runmb[1], runmb[2], xx[9], S12 );
	R1( runmb[2], runmb[3], runmb[0], runmb[1], xx[10], S13 );
	R1( runmb[1], runmb[2], runmb[3], runmb[0], xx[11], S14 );

	R1( runmb[0], runmb[1], runmb[2], runmb[3], xx[12], S11 );
	R1( runmb[3], runmb[0], runmb[1], runmb[2], xx[13], S12 );
	R1( runmb[2], runmb[3], runmb[0], runmb[1], xx[14], S13 );
	R1( runmb[1], runmb[2], runmb[3], runmb[0], xx[15], S14 );

#undef F
#undef R1
#undef S11
#undef S12
#undef S13
#undef S14

	// Round 2
#define G(x,y,z) ( ((x) & (y)) | ( (x) & (z) ) | ( (y) & (z) ) )
#define R2(a,b,c,d,x,s) \
	(a) += G( (b), (c), (d) ) + (x) + (uint32_t)0x5a827999; \
	(a) = RSL( (a), (s) )
#define S21	3
#define S22	5
#define S23	9
#define S24	13

	R2( runmb[0], runmb[1], runmb[2], runmb[3], xx[0], S21 );
	R2( runmb[3], runmb[0], runmb[1], runmb[2], xx[4], S22 );
	R2( runmb[2], runmb[3], runmb[0], runmb[1], xx[8], S23 );
	R2( runmb[1], runmb[2], runmb[3], runmb[0], xx[12], S24 );

	R2( runmb[0], runmb[1], runmb[2], runmb[3], xx[1], S21 );
	R2( runmb[3], runmb[0], runmb[1], runmb[2], xx[5], S22 );
	R2( runmb[2], runmb[3], runmb[0], runmb[1], xx[9], S23 );
	R2( runmb[1], runmb[2], runmb[3], runmb[0], xx[13], S24 );

	R2( runmb[0], runmb[1], runmb[2], runmb[3], xx[2], S21 );
	R2( runmb[3], runmb[0], runmb[1], runmb[2], xx[6], S22 );
	R2( runmb[2], runmb[3], runmb[0], runmb[1], xx[10], S23 );
	R2( runmb[1], runmb[2], runmb[3], runmb[0], xx[14], S24 );

	R2( runmb[0], runmb[1], runmb[2], runmb[3], xx[3], S21 );
	R2( runmb[3], runmb[0], runmb[1], runmb[2], xx[7], S22 );
	R2( runmb[2], runmb[3], runmb[0], runmb[1], xx[11], S23 );
	R2( runmb[1], runmb[2], runmb[3], runmb[0], xx[15], S24 );

#undef G
#undef R2
#undef S21
#undef S22
#undef S23
#undef S24

	// Round 3
#define H(x,y,z) ( (x) ^ (y) ^ (z) )
#define R3(a,b,c,d,x,s) \
	(a) += H( (b), (c), (d) ) + (x) + (uint32_t)0x6ed9eba1; \
	(a) = RSL( (a), (s) )
#define S31	3
#define S32	9
#define S33	11
#define S34	15

	R3( runmb[0], runmb[1], runmb[2], runmb[3], xx[0], S31 );
	R3( runmb[3], runmb[0], runmb[1], runmb[2], xx[8], S32 );
	R3( runmb[2], runmb[3], runmb[0], runmb[1], xx[4], S33 );
	R3( runmb[1], runmb[2], runmb[3], runmb[0], xx[12], S34 );

	R3( runmb[0], runmb[1], runmb[2], runmb[3], xx[2], S31 );
	R3( runmb[3], runmb[0], runmb[1], runmb[2], xx[10], S32 );
	R3( runmb[2], runmb[3], runmb[0], runmb[1], xx[6], S33 );
	R3( runmb[1], runmb[2], runmb[3], runmb[0], xx[14], S34 );

	R3( runmb[0], runmb[1], runmb[2], runmb[3], xx[1], S31 );
	R3( runmb[3], runmb[0], runmb[1], runmb[2], xx[9], S32 );
	R3( runmb[2], runmb[3], runmb[0], runmb[1], xx[5], S33 );
	R3( runmb[1], runmb[2], runmb[3], runmb[0], xx[13], S34 );

	R3( runmb[0], runmb[1], runmb[2], runmb[3], xx[3], S31 );
	R3( runmb[3], runmb[0], runmb[1], runmb[2], xx[11], S32 );
	R3( runmb[2], runmb[3], runmb[0], runmb[1], xx[7], S33 );
	R3( runmb[1], runmb[2], runmb[3], runmb[0], xx[15], S34 );

#undef H
#undef R3
#undef S31
#undef S32
#undef S33
#undef S34


	// end
	md4->mb[0] += runmb[0];
	md4->mb[1] += runmb[1];
	md4->mb[2] += runmb[2];
	md4->mb[3] += runmb[3];

	return;
}

